﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.InfoPath;
using System.Xml.XPath;
using System.Xml;

namespace InfoPathDebugger.RuleManager
{
    /// <summary>
    /// A class to manage rule operations.
    /// </summary>
    public class RuleManager
    {
        /// <summary>
        /// A class to manage rule operations.
        /// </summary>
        public RuleManager()
        {
        }

        /// <summary>
        /// A class to manage rule operations.
        /// </summary>
        /// <param name="dataSources">All data sources of an InfoPath template.</param>
        /// <param name="mainDataSource">The main data source of an InfoPath template.</param>
        /// <param name="userRole">This role is used when the rule need to retrieve current user role to do some further operations.</param>
        public RuleManager(DataSourceCollection dataSources, DataSource mainDataSource, DataConnectionCollection dataConnections, String userRole)
        {
            _allDS = dataSources;
            _mainDS = mainDataSource;
            _dataConnections = dataConnections;
            _role = userRole;
        }

        public DataSource MainDataSource
        {
            get
            {
                return _mainDS;
            }
            set
            {
                _mainDS = value;
            }
        }

        public DataSourceCollection AllDataSource
        {
            get
            {
                return _allDS;
            }
            set
            {
                _allDS = value;
            }
        }

        public DataConnectionCollection DataConnections
        {
            get
            {
                return _dataConnections;
            }
            set
            {
                _dataConnections = value;
            }
        }

        /// <summary>
        /// Gets or sets the user role.
        /// This role is used when the rule need to retrieve current user role to do some further operations.
        /// </summary>
        public String Role
        {
            get
            {
                return _role;
            }
            set
            {
                _role = value;
            }
        }

        /// <summary>
        /// Gets or sets Assignment Action Executor.
        /// This executor is used to execute assignment action. It must be assinged before
        /// call 'ExecuteRule', 'ExecuteAction' method.
        /// </summary>
        public IActionExecutor AssignmentActionExecutor
        {
            get
            {
                return _assignmentActionExecutor;
            }
            set
            {
                _assignmentActionExecutor = value;
            }
        }

        /// <summary>
        /// Gets or sets Query Action Executor.
        /// This executor is used to execute query action. It must be assigned before
        /// call 'ExecuteRule', 'ExecuteAction' method.
        /// </summary>
        public IActionExecutor QueryActionExecutor
        {
            get
            {
                return _queryActionExecutor;
            }
            set
            {
                _queryActionExecutor = value;
            }
        }

        /// <summary>
        /// Execute a rule of an InfoPath form.
        /// </summary>
        /// <param name="ruleNav">A navigator points to 'xsf:rule' node.</param>
        /// <returns>true - the rule is executed, false - the rule is not executed</returns>
        public Boolean ExecuteRule(XPathNavigator ruleNav)
        {
            XPathNavigator ruleNavTemp = ruleNav.CreateNavigator();
            String isEnabled = ruleNavTemp.GetAttribute("isEnabled", String.Empty);
            String condition = ruleNavTemp.GetAttribute("condition", String.Empty);

            if (!isRuleExecutable(isEnabled, condition))
            {
                return false;
            }

            if (ruleNavTemp.MoveToChild(XPathNodeType.Element))
            {
                do
                {
                    ExecuteAction(ruleNavTemp);
                } while (ruleNavTemp.MoveToNext(XPathNodeType.Element));
            }

            return true;
        }

        /// <summary>
        /// Execute an action of a rule.
        /// </summary>
        /// <param name="actionNav">A navigator points to 'xsf:assignmentAction', 'xsf:queryAction'...</param>
        public void ExecuteAction(XPathNavigator actionNav)
        {
            String actionName = actionNav.LocalName;
            switch (actionName)
            {
                case "assignmentAction":
                    if (_assignmentActionExecutor == null)
                    {
                        throw new MemberAccessException("AssignmentActionExecutor is null.");
                    }
                    _assignmentActionExecutor.Execute(actionNav, getMainDataSourceNavigator(), null);
                    break;
                case "queryAction":
                    if (_queryActionExecutor == null)
                    {
                        throw new MemberAccessException("QueryActionExecutor is null.");
                    }
                    _queryActionExecutor.Execute(actionNav, getMainDataSourceNavigator(), _dataConnections);
                    break;
                default:
                    break;
            }
        }

        virtual protected XPathNavigator getMainDataSourceNavigator()
        {
            XPathNavigator nav = _mainDS.CreateNavigator();

            // If the namespace dictionary is null, create it.
            if (_nsDict == null)
            {
                _nsDict = XPathHelper.RetrieveNamespaces(nav);
            }

            return nav;
        }



        private Boolean isRuleExecutable(String isEnabled, String condition)
        {
            // If the rule is disabled, skip it.
            if (String.Compare(isEnabled, "no", true) == 0)
            {
                return false;
            }

            // If the condition is empty, the rule will always execute.
            if (String.IsNullOrEmpty(condition))
            {
                return true;
            }

            return evaluateCondition(condition);
        }

        private Boolean isConditionTrue(String condition)
        {
            if (condition.Contains(" = "))
            {
                return processCompareCondition(condition, " = ");
            }
            else if (condition.Contains(" != "))
            {
                return processCompareCondition(condition, " != ");
            }
            else if (condition.Contains("contains("))
            {
                Int32 startIndex = condition.IndexOf("contains(") + 9;
                Int32 endIndex = condition.IndexOf(")", startIndex) - 1;
                String expression = condition.Substring(startIndex, endIndex - startIndex + 1);

                return processCompareCondition(expression, ", ");
            }

            return false;
        }

        private Boolean evaluateCondition(String condition)
        {
            // Extract sub conditions by "or".
            String[] subConditions = condition.Split(new String[] { " or " }, StringSplitOptions.RemoveEmptyEntries);

            Boolean result = false;
            foreach (String subCondition in subConditions)
            {
                result = result || evaluateAndCondition(subCondition);
            }

            return result;
        }

        /// <summary>
        /// Process subcontions unioned by "and" word.
        /// </summary>
        private Boolean evaluateAndCondition(String condition)
        {
            // Split the condition to subcontions
            String[] subConditions = condition.Split(new String[]{" and "}, StringSplitOptions.RemoveEmptyEntries);

            Boolean result = true;

            // Evaluate all subconditions.
            foreach (String subCondition in subConditions)
            {
                result = result && isConditionTrue(subCondition);
            }

            return result;
        }

        private Boolean processCompareCondition(String condition, String symbol)
        {
            String[] conditionArray = condition.Split(new String[] { symbol }, StringSplitOptions.RemoveEmptyEntries);
            String xpath = conditionArray[0];
            String value = conditionArray[1];

            // If value is from node, extract it.
            if (value.Contains("my:"))
            {
                value = retrieveDataSourceNodeValue(value);
            }
            
            // Remove '"'.
            value = value.Replace("\"", "").Trim();

            String nodeValue = String.Empty;
            if (xpath.Equals("xdXDocument:get-Role()"))
            {
                if (String.IsNullOrEmpty(_role))
                {
                    throw new MemberAccessException("The role information is null. This rule ask for the user role. Please specifies the user role before execute this rule.");
                }

                nodeValue = _role;
            }
            else
            {
                nodeValue = retrieveDataSourceNodeValue(xpath);
            }

            Boolean result = false;
            switch (symbol)
            {
                case " = ":
                    result = (nodeValue == value);
                    break;
                case " != ":
                    result = (nodeValue != value);
                    break;
                case ", ":
                    result = nodeValue.Contains(value);
                    break;
                default:
                    break;
            }

            return result;
        }

        private String retrieveDataSourceNodeValue(String xpath)
        {
            XPathNavigator nav = getMainDataSourceNavigator();
            String nodeValue = XPathHelper.GetNodeValue(nav, "//" + xpath, "my");

            return nodeValue;
        }

        protected IDictionary<String, String> _nsDict;
        private DataSourceCollection _allDS;
        private DataSource _mainDS;
        private DataConnectionCollection _dataConnections;

        private IActionExecutor _assignmentActionExecutor = null;
        private IActionExecutor _queryActionExecutor = null;
        private String _role = null;
    }
}
